from datetime import date, datetime, timedelta
import json
import logging
import os
import sys
import time
from openai import OpenAI
from tqsdk import TqApi, TqAuth, TargetPosTask, TqBacktest,TqAccount
from concurrent.futures import ThreadPoolExecutor
import threading
from hybrid_signal import BybridSignal, BybridData

# 策略参数配置

PROFIT_LOCK_DURATION = timedelta(minutes=60)   # 盈利锁定时长
STOP_LOSS_DURATION = timedelta(minutes=120)    # 最大亏损容忍时长

LOCK = threading.Lock()      # 线程锁

class TradingStrategy:

    
    def __init__(self,symbol:str,tqsdk:TqApi,ds:OpenAI,trade_volume:int = 1,loss_point:int = 10,reasoning_internal=15):
        self.api = tqsdk 
        self.signal_generator = BybridSignal(ds,model="deepseek-r1",symbol=symbol,remind=12)
        self.trade_volume = trade_volume
        self.loss_point = loss_point
        self.reasoning_internal = timedelta(minutes=reasoning_internal)
        # 仓位管理相关
        self.pos_task = TargetPosTask(self.api, symbol)
        self.is_reasoning = False
        # 加载运行时变量
        self.perist_file = f"persist/trading_strategy_{symbol}.json"
        self.position:int = 0        # 实际持仓方向
        self.entry_price:float = .0     # 入场价格
        self.highest_high:float = .0    # 多仓最高价
        self.lowest_low = float('inf')  # 空仓最低价
        self.open_time = None      # 开仓时间戳
        self.load_runtime_data()
        # 初始化数据
        self.quote = self.api.get_quote(symbol)
        self.data = BybridData()
        # 各周期K线数据（排除未完成K线）
        self.data.dk = self.api.get_kline_serial(symbol,86400,30)[:-1]
        self.data.hk = self.api.get_kline_serial(symbol,3600,60)[:-1]
        self.data.mk = self.api.get_kline_serial(symbol,60,120)[:-1]
        self.data.m30k = self.api.get_kline_serial(symbol,1800,120)[:-1]
        self.data.m15k = self.api.get_kline_serial(symbol,900,120)[:-1]
        self.data.m5k = self.api.get_kline_serial(symbol,300,120)[:-1]
        
        # 异步处理
        self.last_reasoning_time = self.get_current_time()

    def load_runtime_data(self):
        #加载运行时数据
        if os.path.exists(self.perist_file):
            with open(self.perist_file, 'r') as file:
                data = json.load(file)
                self.position = int(data["position"])
                self.highest_high = data["highest_high"]
                self.lowest_low = data["lowest_low"]
                self.entry_price = data["entry_price"]
                if "open_time" in data:
                    self.open_time = datetime.strptime(data["open_time"], "%Y-%m-%d %H:%M:%S")
        

    def save_runtime_json(self):
        #保存运行时数据
        data = {}
        data["position"] = self.position
        data["highest_high"] = self.highest_high
        data["lowest_low"] = self.lowest_low
        data["entry_price"] = self.entry_price
        if self.open_time:
            data["open_time"] = self.open_time.strftime("%Y-%m-%d %H:%M:%S")
        with open(self.perist_file, 'w') as file:
            json.dump(data, file)

    def check_stop_loss(self):

        """止损条件检查"""
        last_price = self.quote.last_price
        price_tick = self.quote.price_tick
        
        if self.position == 1:
            if last_price > self.highest_high:
                self.highest_high = last_price
                self.save_runtime_json()
            drawdown = self.highest_high - last_price
            if drawdown >= self.loss_point * price_tick:
                self.close_position("多仓止损")
                
        elif self.position == -1:
            if last_price < self.lowest_low:
                self.lowest_low = last_price
                self.save_runtime_json()
            drawdown = last_price - self.lowest_low
            if drawdown >= self.loss_point * price_tick:
                self.close_position("空仓止损")

    def open_position(self, signal):
        """执行开仓操作"""
        with LOCK:
            volume = signal * self.trade_volume
            self.entry_price = self.quote.last_price  # 记录入场价格       
            self.open_time = self.get_current_time()  # 记录开仓时间
            self.position = signal
            try:
                self.pos_task.set_target_volume(volume)
                logging.info(f"[{self.open_time.strftime('%H:%M:%S')}] 新开仓 | 方向: {'多' if signal==1 else '空'} | 价格: {self.entry_price:.2f}")
            except Exception as e:
                logging.info(f"信号处理异常: {str(e)}")
            # 初始化极值价格
            if signal == 1:
                self.highest_high = self.entry_price
            else:
                self.lowest_low = self.entry_price
            self.save_runtime_json()
    
    def close_position(self, reason):
        """执行平仓操作"""
        with LOCK:
            self.pos_task.set_target_volume(0)
             # 记录平仓信息
            holding_duration = (self.get_current_time() - self.open_time).total_seconds() / 60
            logging.info(f"[{self.quote.datetime}] {reason} | 价格: {self.quote.last_price:.2f} | 持仓时间: {holding_duration:.1f}分钟")
            
            # 重置状态
            self.position = 0
            self.open_time = None
            self.highest_high = 0
            self.lowest_low = float('inf')
            self.save_runtime_json()
      

    def check_time_exit(self):
        """时间相关平仓条件检查"""
        if self.position == 0 or not self.open_time:
            return
        
        current_profit = 0
        current_price = self.quote.last_price

        # 计算浮动盈亏 
        if self.position == 1 :
            current_profit = (current_price - self.entry_price) * self.quote.price_tick * self.position
        elif self.position == -1 :
            current_profit = (self.entry_price - current_price) * self.quote.price_tick * self.position
        
        holding_duration = self.get_current_time() - self.open_time
        
        # 时间窗口止盈
        if current_profit > 0:
            # 盈利持续锁定
            if holding_duration >= PROFIT_LOCK_DURATION:
                self.close_position(f"盈利锁定（持盈{holding_duration.total_seconds()/60:.1f}分钟）")
                return
        else:
            # 时间止损
            if holding_duration >= STOP_LOSS_DURATION :
                self.close_position(f"时间止损（亏损{abs(current_profit):.2f}）")

    def get_current_time(self):
        return datetime.strptime(self.quote.datetime, "%Y-%m-%d %H:%M:%S.%f")

    def run_reasoning(self):
        try:
            self.is_reasoning = True
            reasoning_target_time = self.last_reasoning_time
            self.last_reasoning_time = self.get_current_time()
            signal = self.signal_generator.reasoning_signal(self.data,reasoning_target_time)
            
            self.is_reasoning = False
            if signal not in [-1, 0, 1]:
                return
            if signal != 0:
                self.open_position(signal)
        except Exception as e:
            print("Error:", e)

    def is_need_reasoning(self):
        if self.is_reasoning :
            return False
        if self.last_reasoning_time is not None and self.get_current_time() - self.last_reasoning_time < self.reasoning_internal:
            return False
        return True

    def update(self):
        """主运行循环"""
        # 更新极值价格
        if self.position != 0:
            self.check_stop_loss()
            self.check_time_exit()  
        

if __name__ == "__main__":
    if len(sys.argv) > 1:
        is_backtest = sys.argv[1] == "backtest"
    else:
        is_backtest = False
    log_file = datetime.now().strftime("log/trading_%Y-%m-%d_%H%M%S.log")
    logging.basicConfig(filename=log_file, level=logging.INFO) 
    if is_backtest :   
        ds = OpenAI(api_key="xxxx", base_url="https://api.lkeap.cloud.tencent.com/v1")
        tqsdk = TqApi(web_gui=True,backtest=TqBacktest(start_dt=date(2025, 2, 25), end_dt=date(2025, 2, 25)),auth=TqAuth("xxxx", "xxxx"))
    else:
        ds = OpenAI(api_key="xxxx", base_url="https://api.lkeap.cloud.tencent.com/v1")
        tqsdk = TqApi(account=TqAccount("simnow", "xxxx", "xxxx"),web_gui=True,auth=TqAuth("xxxx", "xxxx"))
    
    executor = ThreadPoolExecutor(max_workers=2)
  
    '''
    注意自己配置参数
    '''
    future = None
    strategy_list = [
        TradingStrategy("SHFE.ag2504",tqsdk,ds,1,10),
        TradingStrategy("SHFE.ni2505",tqsdk,ds,1,10),
        TradingStrategy("CZCE.SR505",tqsdk,ds,1,80),
        TradingStrategy("CZCE.SA505",tqsdk,ds,1,80)
    ]
    reasoning_iterator = 0
    
    while True:
        try:
        
            tqsdk.wait_update()
            for strategy in strategy_list:
                strategy.update()
            if future and not future.done() :
                if is_backtest:
                    time.sleep(60)
                continue 
            reasoning_strategy = strategy_list[reasoning_iterator]
            if reasoning_strategy.is_need_reasoning():   
                # 提交信号生成任务
                future = executor.submit(reasoning_strategy.run_reasoning)
            reasoning_iterator = (reasoning_iterator+1)%len(strategy_list)
        except Exception as e:
            print("Error:", e)
    
    executor.shutdown()
    tqsdk.close()
